package org.snlab.runtime;

import java.util.List;

import com.google.protobuf.ByteString;

import org.snlab.proto.Ddmpt.Empty;
import org.snlab.proto.Ddmpt.Msg;
import org.snlab.proto.Ddmpt.Property;
import org.snlab.proto.Ddmpt.VerifyTask;
import org.snlab.proto.Ddmpt.Vnode;
import org.snlab.proto.Ddmpt.Vnodes;
import org.snlab.proto.RuntimeGrpc.RuntimeImplBase;

import io.grpc.stub.StreamObserver;

class RuntimeImpl extends RuntimeImplBase {
    private Daemon daemon;

    public RuntimeImpl(Daemon daemon) {
        this.daemon = daemon;
    }

    // private void doMap(int hs, List<Nexthop> nexthops) {
    //     BDDEngine bddEngine = daemon.getDIB().getBDDEngine();
    //     for (Nexthop nexthop : nexthops) {
    //         for (String port : nexthop.getViaList()) {
    //             int portHs = daemon.getDIB().getPortToPred().getOrDefault(port, 0);
    //             int intersection = daemon.getDIB().getBDDEngine().and(hs, portHs);
    //             if (intersection != 0) {
    //                 ByteString bs = ByteString.copyFrom(bddEngine.serialize(intersection));
    //                 Msg m = Msg.newBuilder().setHs(bs).setTo(nexthop.getVid()).build();

    //                 String[] addrArr = nexthop.getAddr().split(":");
    //                 Channel c = ManagedChannelBuilder.forAddress(addrArr[0], Integer.parseInt(addrArr[1]))
    //                         .usePlaintext().build();
    //                 NotifyBlockingStub stub = NotifyGrpc.newBlockingStub(c);
    //                 stub.process(m);

    //                 // TODO: manage the channels, check grpc reuse managed channels
    //                 // TODO: reply first then send msg to downstreams
    //             }
    //         }
    //     }
    // }

    // private void processMsg(Msg request) {
    //     BDDEngine bddEngine = daemon.getDIB().getBDDEngine();
    //     List<Nexthop> nexthops = daemon.getRouter().route(request.getTo());
    //     if (nexthops.size() == 0) {
    //         Daemon.logger.log("reach dst " + daemon.getConfig().getName());
    //     } else {
    //         int hs = bddEngine.deserialize(request.getHs().toByteArray());
    //         this.checkViolation(hs, nexthops);
    //         doMap(hs, nexthops);
    //     }
    // }

    @Override
    public void addVnode(Vnode vnode, StreamObserver<Empty> responseObserver) {
        // System.out.println(vnode);
        daemon.getVrouter().add(vnode);
        responseObserver.onNext(Empty.newBuilder().build());
        responseObserver.onCompleted();
    }

    @Override
    public void addVnodes(Vnodes vnodes, StreamObserver<Empty> responseObserver) {
        // System.out.println(vnodes);
        daemon.getVrouter().add(vnodes);
        responseObserver.onNext(Empty.newBuilder().build());
        responseObserver.onCompleted();
    }

    @Override
    public void showVnodes(Empty request, StreamObserver<Vnodes> responseObserver) {
        Vnodes vnodes = Vnodes.newBuilder().addAllVnode(daemon.getVrouter().getVnodes()).build();
        responseObserver.onNext(vnodes);
        responseObserver.onCompleted();
    }

    @Override
    public void process(Msg request, StreamObserver<Empty> responseObserver) {
        // System.out.println("received vid: " + request.getTo());

        Empty rp = Empty.newBuilder().build();
        responseObserver.onNext(rp);
        responseObserver.onCompleted();
        BDDEngine bddEngine = daemon.getDIB().getBDDEngine();
        int hs = bddEngine.deserialize(request.getHs().toByteArray());
        Message message = new Message(request, hs);
        daemon.getVrouter().route(message, hs);
    }

    // private void checkViolation(int hs, List<Nexthop> nexthops) {
    //     BDDEngine bddEngine = daemon.getDIB().getBDDEngine();
    //     if (nexthops.size() == 0) {
    //         return;
    //     }
    //     int outHs = 0;
    //     for (Nexthop nexthop : nexthops) {
    //         for (String port : nexthop.getViaList()) {
    //             System.out.println(port);
    //             int h = daemon.getDIB().getPortToPred().getOrDefault(port, 0); // in case no rules covers a port, e.g.
    //                                                                            // I2 wash xe-0/0/0
    //             outHs = daemon.getDIB().getBDDEngine().or(outHs, h);
    //         }
    //     }
    //     if (!bddEngine.subset(hs, outHs)) {
    //         System.out.print("violation");
    //         Daemon.logger.log("found violation");
    //     }
    // }

    // @Override
    // public void insertEntry(Entry request, StreamObserver<Empty> responseObserver) {
    //     daemon.getRouter().insert(request.getVid(), request.getNexthopsList());
    //     responseObserver.onNext(Empty.newBuilder().build());
    //     responseObserver.onCompleted();
    // }

    // @Override
    // public void showVroutes(Empty request, StreamObserver<Vroutes> responseObserver) {
    //     Vroutes.Builder vroutes = Vroutes.newBuilder();
    //     for (Map.Entry<Long, List<Nexthop>> e : daemon.getRouter().getTable().entrySet()) {
    //         Entry entry = Entry.newBuilder().setVid(e.getKey()).addAllNexthops(e.getValue()).build();
    //         vroutes.addEntires(entry);
    //     }
    //     Vroutes vrs = vroutes.build();
    //     responseObserver.onNext(vrs);
    //     responseObserver.onCompleted();
    // }

    @Override
    public void verify(VerifyTask request, StreamObserver<org.snlab.proto.Ddmpt.Empty> responseObserver) {
        Daemon.logger.log("start propagation");
        responseObserver.onNext(Empty.newBuilder().build());
        responseObserver.onCompleted();
        BDDEngine bddEngine = daemon.getDIB().getBDDEngine();
        int hs = bddEngine.encodeIP(request.getIp(), request.getPrefix());
        ByteString bs = ByteString.copyFrom(bddEngine.serialize(hs));
        Msg msg = Msg.newBuilder().setHs(bs).setBdd(hs).setTo(request.getVid()).setProperty(Property.newBuilder().setCount(1).build()).build();
        Message message = new Message(msg, hs);
        daemon.getVrouter().route(message, hs);
        // checkViolation(hs, nexthops);
        // doMap(hs, nexthops);

        // ByteString bs = ByteString.copyFrom(bddEngine.serialize(hs));
        // Msg m = Msg.newBuilder().setHs(bs).setVid(request.getVid()).build();

        // Channel c = ManagedChannelBuilder.forAddress("localhost", daemon.getConfig().getPort()).usePlaintext().build();
        // NotifyBlockingStub stub = NotifyGrpc.newBlockingStub(c);
        // stub.process(m);
        // responseObserver.onNext(org.snlab.proto.Ddmpt.Empty.newBuilder().build());
        // responseObserver.onCompleted();
    }

    @Override
    public void deleteRule(Empty request, StreamObserver<Empty> responseObserver) {
        Daemon.logger.log("rule deleted");
        daemon.getVrouter().resetTs();
        // daemon.getVrouter().resetCpuTs();
        int change = daemon.getDIB().deleteRule(daemon.getDIB().getRules().last());
        daemon.getVrouter().reroute(change);
        responseObserver.onNext(Empty.newBuilder().build());
        responseObserver.onCompleted();
    }

    @Override
    public void dump(Empty request, StreamObserver<Empty> responseObserver) {
        daemon.getVrouter().dump();
        responseObserver.onNext(Empty.newBuilder().build());
        responseObserver.onCompleted();
    }

    @Override
    public void replay(Empty request, StreamObserver<Empty> responseObserver) {
        daemon.getVrouter().replayTrace();
        responseObserver.onNext(Empty.newBuilder().build());
        responseObserver.onCompleted();
    }
}
